#!/usr/bin/python3
#
# Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#

from typing import NamedTuple, List
from os import path, listdir

REPO_BASE = path.abspath(path.join(path.dirname(path.realpath(__file__)), '..', '..'))

class Example(NamedTuple):
    id: str
    path: str
    title: str


class MultiExample(NamedTuple):
    id: str
    paths: List[str]
    title: str


LINK_TEMPLATE = '* [link mysql.examples.{example.id} {example.title}]'
SECTION_TEMPLATE = '''
[section:{example.id} {example.title}]

This example assumes you have gone through the [link mysql.examples.setup setup].

{example_cpps}

[endsect]
'''

TEMPLATE='''[/
    Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
   
    Distributed under the Boost Software License, Version 1.0. (See accompanying
    file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
]

[/ This file was auto-generated by examples_qbk.py. Do not edit directly ]

[section:examples Examples]

To run the examples, please go through the [link mysql.examples.setup setup] first.

Here is a list of available examples:

[heading Tutorials]

Self-contained programs demonstrating the basic concepts.

{tutorial_links}

[heading Simple programs]

Self-contained programs demonstrating more advanced concepts and techniques.

{simple_links}

[heading Advanced examples]

Programs implementing real-world functionality.

{advanced_links}
# [@https://github.com/anarthal/servertech-chat The BoostServerTech chat project uses Boost.MySQL and Boost.Redis to implement a chat server]

[heading Setup]

To run the examples, you need a MySQL server you can connect to.
Examples make use of a database named `boost_mysql_examples`.
The server hostname and credentials (username and password) are passed 
to the examples via the command line.

You can spin up a server quickly by using Docker:

[!teletype]
```
    # Remove the "-v /var/run/mysqld:/var/run/mysqld" part if you don't need UNIX sockets
    > docker run --name some-mysql -p 3306:3306 -v /var/run/mysqld:/var/run/mysqld -d -e MYSQL_ROOT_PASSWORD= -e MYSQL_ALLOW_EMPTY_PASSWORD=1 -d mysql

    # All the required data can be loaded by running example/db_setup.sql.
    # If you're using the above container, the root user has a blank password
    > mysql -u root < example/db_setup.sql
```

Please note that this container is just for demonstrative purposes,
and is not suitable for production.

The root MySQL user for these containers is `root` and has an empty password.

{all_examples}

[endsect]

'''

# List all examples here
TUTORIALS = [
    Example('tutorial_sync',                 '1_tutorial/1_sync.cpp',                 'Tutorial 1 listing: hello world!'),
    Example('tutorial_async',                '1_tutorial/2_async.cpp',                'Tutorial 2 listing: going async with C++20 coroutines'),
    Example('tutorial_with_params',          '1_tutorial/3_with_params.cpp',          'Tutorial 3 listing: queries with parameters'),
    Example('tutorial_static_interface',     '1_tutorial/4_static_interface.cpp',     'Tutorial 4 listing: the static interface'),
    Example('tutorial_updates_transactions', '1_tutorial/5_updates_transactions.cpp', 'Tutorial 5 listing: UPDATEs, transactions and multi-queries'),
    Example('tutorial_connection_pool',      '1_tutorial/6_connection_pool.cpp',      'Tutorial 6 listing: connection pools'),
    Example('tutorial_error_handling',       '1_tutorial/7_error_handling.cpp',       'Tutorial 7 listing: error handling'),
]

SIMPLE_EXAMPLES = [
    Example('inserts', '2_simple/inserts.cpp', 'INSERTs, last_insert_id() and NULL values'),
    Example('deletes', '2_simple/deletes.cpp', 'DELETEs and affected_rows()'),
    Example('prepared_statements', '2_simple/prepared_statements.cpp', 'Prepared statements'),
    Example('disable_tls', '2_simple/disable_tls.cpp', 'Disabling TLS for a connection'),
    Example('tls_certificate_verification', '2_simple/tls_certificate_verification.cpp', 'Setting TLS options: enabling TLS certificate verification'),
    Example('metadata', '2_simple/metadata.cpp', 'Metadata'),
    Example('multi_function', '2_simple/multi_function.cpp', 'Reading rows in batches with multi-function operations'),
    Example('callbacks', '2_simple/callbacks.cpp', 'Callbacks (async functions in C++11)'),
    Example('coroutines_cpp11', '2_simple/coroutines_cpp11.cpp', 'Stackful coroutines (async functions in C++11)'),
    Example('unix_socket', '2_simple/unix_socket.cpp', 'UNIX sockets'),
    Example('batch_inserts', '2_simple/batch_inserts.cpp', 'Batch inserts using client-side query formatting'),
    Example('batch_inserts_generic', '2_simple/batch_inserts_generic.cpp', 'Generic batch inserts with Boost.Describe'),
    Example('dynamic_filters', '2_simple/dynamic_filters.cpp', 'Queries with dynamic filters'),
    Example('patch_updates', '2_simple/patch_updates.cpp', 'Dynamic UPDATE queries with PATCH-like semantics'),
    Example('source_script', '2_simple/source_script.cpp', 'Sourcing a .sql file using multi-queries'),
    Example('pipeline', '2_simple/pipeline.cpp', '(Experimental) Pipelines'),
]

ADVANCED_EXAMPLES = [
    MultiExample('http_server_cpp20', [
        '3_advanced/http_server_cpp20/main.cpp',
        '3_advanced/http_server_cpp20/types.hpp',
        '3_advanced/http_server_cpp20/error.hpp',
        '3_advanced/http_server_cpp20/error.cpp',
        '3_advanced/http_server_cpp20/repository.hpp',
        '3_advanced/http_server_cpp20/repository.cpp',
        '3_advanced/http_server_cpp20/handle_request.hpp',
        '3_advanced/http_server_cpp20/handle_request.cpp',
        '3_advanced/http_server_cpp20/server.hpp',
        '3_advanced/http_server_cpp20/server.cpp',
    ], 'A REST API server that uses C++20 coroutines'),

    MultiExample('http_server_cpp14_coroutines', [
        '3_advanced/http_server_cpp14_coroutines/main.cpp',
        '3_advanced/http_server_cpp14_coroutines/types.hpp',
        '3_advanced/http_server_cpp14_coroutines/repository.hpp',
        '3_advanced/http_server_cpp14_coroutines/repository.cpp',
        '3_advanced/http_server_cpp14_coroutines/handle_request.hpp',
        '3_advanced/http_server_cpp14_coroutines/handle_request.cpp',
        '3_advanced/http_server_cpp14_coroutines/server.hpp',
        '3_advanced/http_server_cpp14_coroutines/server.cpp',
    ], 'A C++14 REST API server that uses asio::yield_context'),
]

ALL_EXAMPLES = TUTORIALS + SIMPLE_EXAMPLES + ADVANCED_EXAMPLES

def _render_links(examples: List[Example]) -> str:
    return '\n'.join(LINK_TEMPLATE.format(example=elm) for elm in examples)

def _write_file(relpath: List[str], contents: str) -> None:
    output_file = path.join(REPO_BASE, *relpath)
    with open(output_file, 'wt') as f:
        f.write(contents)

def _render_simple_cpp(id: str) -> str:
    return f'[example_{id}]'

def _render_multi_cpp(id: str, paths: List[str]) -> str:
    def get_file_id(p: str):
        # File IDs follow the below convention
        converted_id = path.basename(p).replace('.', '_')
        return f'{id}_{converted_id}'

    return '\n\n'.join(_render_simple_cpp(get_file_id(p)) for p in paths)

def _collect_snippets() -> List[str]:
    snippets_relpath = ['test', 'integration', 'test', 'snippets']
    return [
        path.join(*snippets_relpath, p)
        for p in  listdir(path.join(REPO_BASE, *snippets_relpath))
    ]

def _replace_imports(import_contents: str) -> None:
    # Read the file
    with open(path.join(REPO_BASE, 'doc', 'qbk', '00_main.qbk'), 'rt') as f:
        contents = f.read()
    
    # Replace
    begin_marker = '[/ AUTOGENERATED IMPORTS BEGIN ]\n'
    end_marker = '\n[/ AUTOGENERATED IMPORTS END ]'
    begin_pos = contents.find(begin_marker)
    end_pos = contents.find(end_marker)
    assert begin_pos != -1
    assert end_pos != -1
    final_contents = contents[:begin_pos + len(begin_marker)] + import_contents + contents[end_pos:]

    # Write the file
    _write_file(['doc', 'qbk', '00_main.qbk'], final_contents)


def main():
    # Collect all files to be imported
    example_paths = [e.path for e in ALL_EXAMPLES if isinstance(e, Example)]
    for p in [e.paths for e in ALL_EXAMPLES if isinstance(e, MultiExample)]:
        example_paths += p
    all_paths = [f'example/{p}' for p in example_paths]
    all_paths += _collect_snippets()
    

    # Render
    import_contents = '\n'.join(f'[import ../../{p}]' for p in all_paths)
    example_contents = TEMPLATE.format(
        tutorial_links=_render_links(TUTORIALS),
        simple_links=_render_links(SIMPLE_EXAMPLES),
        advanced_links='',
        all_examples='\n\n\n'.join(SECTION_TEMPLATE.format(
            example=elm,
            example_cpps=_render_multi_cpp(elm.id, elm.paths) if isinstance(elm, MultiExample) else _render_simple_cpp(elm.id)
        ) for elm in ALL_EXAMPLES)
    )

    # Write to output file
    _replace_imports(import_contents)
    _write_file(['doc', 'qbk', '21_examples.qbk'], example_contents)
    

if __name__ == '__main__':
    main()